#!/usr/bin/perl -w
use strict;
use warnings;
#use lib "/home/ivashina";
use SemanticDiff;
use NonmemHandler;

#########################################################################################################################
# compare.pl -- command-line friendly interface to XML::SemanticDiff which allows to compare 2 files
#               qualify.xml obtained with command 'perl autolog.pl log.xml qualify qualify.xml' to 
#               the reference qualify.xml shipped with NMQual. 
#               (Both files are execution logs of various examples produced by NONMEM)               
# Author: Julia Ivashina (jivashina@gmail.com) 
#
# Value differences reported by this program are limited to differences in Theta, Sigma, Omega
# and Minimum Objective Function Value. Modify XML/NonmemHandler.pm if interested in other values.  
# We consider dissimilarity index of 5 be grudgingly acceptable (default), 1 be fine, and 0 be ideal.
# Dissimilarity Index: abs(OUR_VALUE/REF_VALUE - 1)*100 
# Percent difference: 100 * abs(OUR_VALUE-REF_VALUE) / ((OUR_VALUE+REF_VALUE)*0.5) 
# Rogue and missing elements/attributes will also be reported. 
#
# DISCLAIMER: 
#############
#This program is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public License
#as published by the Free Software Foundation; either version 2
#of the License, or (at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# Arguments: 
#############
# ARG1 = path to qualify.xml produced by you (REQUIRED)
# ARG2 = path to the reference qualify.xml shipped with NMQual (REQUIRED)
# ARG3 = numeric index of how dissimilar you allow Thetas to be before you want to see them on the report, 
#        if skipped defaults to 5 (OPTIONAL)
# ARG4 = numeric index of how dissimilar you allow Sigmas to be before you want to see them on the report, 
#        if skipped defaults to 5 (OPTIONAL)
# ARG5 = numeric index of how dissimilar you allow Omegas to be before you want to see them on the report, 
#        if skipped defaults to 5 (OPTIONAL)
# ARG6 = numeric index of how dissimilar you allow Minimum Objective Function values to be before you want to see them on the report, 
#        if skipped defaults to 5 (OPTIONAL)
# ARG7 = boolean report_rogue, 1 = report extra elements and attributes found in our qualify.xml, 0 = skip reporting (default)
# ARG8 = boolean report_missing, 1 = report elements and attributes missing from our qualify.xml, 0 = skip reporting (default)
#
# Examples:
#############
# perl compare.pl ./test/my_qualify.xml ./test/ref_qualify.xml
# Compares 2 files reporting all Thetas/Sigma/Omega and Minimum Objective Function values that are less than 95% (default) similar.
#
# perl compare.pl /opt/tools/nm72/nmqual/qualify.xml /nmqual-8.1.5/doc/nix/qualify.xml 2 2 2 0 
# Compares 2 files reporting all Thetas, Sigmas, Omegas that are less than 98% similar, 
# and all Minimum Objective Function values that are less than 100% similar
##########################################################################################################################

usage() if (scalar @ARGV < 2);
my @arguments = split(/\s+/,@ARGV);
my $my_file = $ARGV[0];
my $ref_file = $ARGV[1];

my ($ath, $asi, $aom, $ami, $rogue, $missing) = @ARGV[2,3,4,5,6,7];

# defaults for all acceptable dissimilarity indexes 
$ath = 5 unless defined $ath;
$asi = 5 unless defined $asi;
$aom = 5 unless defined $aom;
$ami = 5 unless defined $ami;
$rogue = 0 unless defined $rogue;
$missing = 0 unless defined $missing;

my($day, $month, $year)=(localtime)[3..5];

print " Date: $day-".($month+1)."-".($year+1900)."\n";
print " Comparison of NONMEM qualification results to the reference data \n\n";

print " DISCLAIMER: Value differences reported by this program are limited to differences in Theta, Sigma, Omega \n";
print "             and Minimum Objective Function Value.\n\n";  
print " We consider 5% dissimilarity of our results from the reference as acceptable. \n\n";
print " Dissimilarity Index: abs(OUR_VALUE/REF_VALUE- 1)*100\n"; 
print " Percent difference: 100 * abs(OUR_VALUE-REF_VALUE) / ((OUR_VALUE+REF_VALUE)*0.5)\n\n"; 

my $h = NonmemHandler->new(accepted_theta_diss => $ath, 
                                              accepted_sigma_diss => $asi, 
                                              accepted_omega_diss => $aom,
                                              accepted_minobj_diss=> $ami,
                                              report_rogue => $rogue,
                                              report_missing => $missing,);

my $diff = SemanticDiff->new(diffhandler => $h, keeplinenums => 1, keepdata => 1); 
my $separator = "=========================================================================\n";

my @results = $diff->compare($ref_file, $my_file);
my @element_diff;
my @rogue_elements;
my @missing_elements;
my @rogue_attrs;
my @missing_attrs;
my @attr_diff;

foreach my $change (@results) {
    if (defined $change->{diss}) {
    	push (@element_diff, $change);
    } elsif ($rogue && $change->{rogue_element}) {
    	push (@rogue_elements, $change);
    } elsif ($missing && $change->{missing_element}) {
    	push (@missing_elements, $change);
    } elsif ($rogue && $change->{rogue_attr}) {
    	push (@rogue_attrs, $change);
    } elsif ($missing && $change->{missing_attr}) {
    	push (@missing_attrs, $change);
    } elsif ($change->{attr_diff}) {
    	push (@attr_diff, $change);
    }	
}

my $total_element_diffs = scalar @element_diff;
my $total_element_rogue = scalar @rogue_elements;
my $total_element_missing = scalar @missing_elements;
my $total_attr_rogue = scalar @rogue_attrs;
my $total_attr_missing = scalar @missing_attrs;
my $total_attr_diffs = scalar @attr_diff;

print " Report summary \n";
print $separator;
print " Total dissimilarities in element values beyond acceptable: $total_element_diffs \n";
if ($rogue) {
	print " Total rogue elements: $total_element_rogue \n";
	print " Total rogue attributes: $total_attr_rogue \n";
}
if ($missing) {
	print " Total missing elements: $total_element_missing \n";
	print " Total missing attributes: $total_attr_missing \n";
}
print " Total differences in attribute values: $total_attr_diffs \n\n";

my $i = 0; 
print $separator;
print "Element value differences \n";
print $separator;
foreach my $change (@element_diff) {
    $i++;
    print "$i) $change->{message} \n" if $change->{message};
    print "Our value: \t\t\t $change->{new_value}\n" if $change->{new_value};
    print "Reference value: \t\t $change->{old_value}\n" if $change->{old_value};
    print "Dissimilarity index***: \t $change->{diss} \n";
    print "Percent difference: \t\t $change->{diff} \n";
    print "Between lines ".($change->{startline}+13)." and ".($change->{endline}+13)."\n" if ($change->{startline} && $change->{endline});
    print "Element: '$change->{context}'\n\n";	
}
print " Total dissimilarities in elements that are beyond acceptable values: $total_element_diffs \n";
print "*** 'Dissimilarity index' is calculated as: abs(OUR_VALUE/REF_VALUE- 1)*100\n";
print "*** 'Percent difference' is calculated as:  100*abs(OUR_VALUE-REF_VALUE)/(abs(OUR_VALUE+REF_VALUE)/2)\n";

if ($rogue) {
	$i = 0;
	print $separator;
	print "Rogue elements \n";
	print $separator;
	foreach my $change (@rogue_elements) {
	    $i++;
	    print "$i) $change->{message} \n" if $change->{message};
	    print "Value '$change->{new_value}'\n" if $change->{new_value};	
	    print "Between lines ".($change->{startline}+13)." and ".($change->{endline}+13)."\n" if ($change->{startline} && $change->{endline});
	    print "Element: '$change->{context}'\n\n";	
	}
	print " Total rogue elements: $total_element_rogue \n";
}

if ($missing) {
	$i = 0;
	print $separator;
	print "Missing elements \n";
	print $separator;
	foreach my $change (@missing_elements) {
	    $i++;
	    print "$i) $change->{message} \n" if $change->{message};
	    print "Value '$change->{old_value}'\n" if $change->{old_value};	
	    print "Between lines ".($change->{startline}+13)." and ".($change->{endline}+13)."\n" if ($change->{startline} && $change->{endline});
	    print "Element: '$change->{context}'\n\n";	
	}
	print " Total missing elements: $total_element_missing \n";
}

if ($rogue) {
	$i = 0;
	print $separator;
	print "Rogue attributes \n";
	print $separator;
	foreach my $change (@rogue_attrs) {
	    $i++;
	    print "$i) $change->{message} \n" if $change->{message};
	    print "Value '$change->{new_value}'\n" if $change->{new_value};	
	    print "Between lines ".($change->{startline}+13)." and ".($change->{endline}+13)."\n" if ($change->{startline} && $change->{endline});
	    print "Attribute: '$change->{context}'\n\n";	
	}
	print " Total rogue attributes: $total_attr_rogue \n";
}

if ($missing) {
	$i = 0;
	print $separator;
	print "Missing attributes \n";
	print $separator;
	foreach my $change (@missing_attrs) {
	    $i++;
	    print "$i) $change->{message} \n" if $change->{message};
	    print "Value '$change->{old_value}'\n" if $change->{old_value};	
	    print "Between lines ".($change->{startline}+13)." and ".($change->{endline}+13)."\n" if ($change->{startline} && $change->{endline});
	    print "Element: '$change->{context}'\n\n";	
	}
	print " Total missing attributes: $total_attr_missing \n";
}

#$i = 0;
#foreach my $change (@attr_diff) {
#    $i++;
#    print "$i) $change->{message} \n" if $change->{message};
#    print "Our value: \t\t\t $change->{new_value}\n" if $change->{new_value};
#    print "Reference value: \t\t $change->{old_value}\n" if $change->{old_value};
#    print "Between lines ".($change->{startline}+13)." and ".($change->{endline}+13)."\n" if ($change->{startline} && $change->{endline});
#    print "Element: '$change->{context}'\n\n";	
#}
#print " Total differences in attribute values: $total_attr_diffs \n\n";


sub usage {
   my $usage = "ucase($0) ref_file.xml my_file.xml [[accepted_theta] [accepted_sigma] [accepted_omega] [accepted_minobj] [report_rogue] [report_missing]]\n"; 
   $usage .= "       By default all accepted dissimilarity indexes are set to 5% unless you specify different\n";
   $usage .= "       By default rogue and missing attributes/elements are not reported\n";
   $usage .= "       Element value differences reported by this program are limited to Theta, Omega, Sigma and Minimum Objective Function Value. \n";
   $usage .= "       Differences in dates, time elapsed, license expiration etc. are ignored \n";
   die "Usage: $usage";
}

exit;

